package com.ctrip.corp.obt.shard.manager;

import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.generic.utils.LogAggregateUtils;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.datasource.DynamicRoutingDataSource;
import com.ctrip.corp.obt.shard.domain.gateway.DataShardSourceGateway;
import com.ctrip.corp.obt.shard.holder.ShardSourceHolder;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.helpers.MessageFormatter;

import com.ctrip.corp.obt.shard.core.ShardDataSource;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static com.ctrip.corp.obt.generic.constants.GenericConstants.DEFAULT;

/**
 * @author marco.zheng
 * @version 1.0
 * @date 2022/03/14
 * @desc 数据分片资源manager1
 * @see
 * @since 1.0
 */
@Slf4j
public class DataShardSourceManager {

    private final DataShardSourceGateway dataShardSourceGateway;
    private final DynamicRoutingDataSource dynamicRoutingDataSource;

    public DataShardSourceManager(DataShardSourceGateway dataShardSourceGateway,
        DynamicRoutingDataSource dynamicRoutingDataSource, DataSource defaultDataSource) {
        this.dataShardSourceGateway = dataShardSourceGateway;
        this.dynamicRoutingDataSource = dynamicRoutingDataSource;
        ShardSourceHolder.putDataSource(DEFAULT, defaultDataSource);
    }

    /**
     * 缓存指定的tenantIds，如果tenantIds为空，则默认会加载所有的datasource
     * 
     * @param tenantIds tenantIds
     * @return
     */
    public Set<String> cacheShardSource(Set<String> tenantIds) {
        Set<ShardDataSource> dataSourceList = loadDataShardSources(tenantIds);
        if (CollectionUtils.isEmpty(dataSourceList)) {
            return Collections.emptySet();
        }
        Set<String> finishLoadSources = new HashSet<>(dataSourceList.size());
        LogAggregateUtils.LogContext logContext = LogAggregateUtils.createLogContext();
        logContext.addLogContext("*******************Start loading multi datasource*******************");
        for (ShardDataSource source : dataSourceList) {
            String tenantId = source.getTenantId();
            if (ShardSourceHolder.contains(tenantId)) {
                // todo 已加载的数据源，更新后二次加载怎么办？
                logContext.addLogContext("Skip load datasource:[{}] cause by repeat", tenantId);
            } else {
                try {
                    ShardSourceHolder.putDataSource(tenantId, source);
                    logContext.addLogContext("Load datasource from {} :[{}] success", source.getSource(), tenantId);
                    finishLoadSources.add(tenantId);
                } catch (Exception ex) {
                    Cat.logEvent("DataShard",
                        "Load.Source",
                        Message.SUCCESS,
                        StringUtils.join("failed for loading tenantIds:", tenantIds, ", the reason is ",
                            ex.getMessage()));
                    log.error(MessageFormatter.format("Load datasource:[{}] failed", tenantId).getMessage(), ex);
                }
            }
        }
        logContext.addLogContext("********************************************************************");
        logContext.info(log);
        return finishLoadSources;
    }

    private Set<ShardDataSource> loadDataShardSources(Set<String> tenantIds) {
        Set<ShardDataSource> dataShardSourceList;
        if (CollectionUtils.isEmpty(tenantIds)) {
            dataShardSourceList = dataShardSourceGateway.getAllSource();
        } else {
            Set<String> tenantIdsCopy = new HashSet<>(tenantIds);
            // 移除已经加载过的数据源，无需重新加载
            tenantIdsCopy.removeIf(ShardSourceHolder::contains);
            dataShardSourceList = this.dataShardSourceGateway.getSourceByTenantIds(tenantIdsCopy);
        }
        return dataShardSourceList;
    }

    /**
     * 初始化data source
     */
    public void initDataSource() {
        log.info("Start init dynamic routing datasource");
        DataSource defaultDataSource = ShardSourceHolder.getDefaultDataSource();
        Map<Object, Object> targetDataSource = ShardSourceHolder.getDataSourceCache();
        // 设置默认的数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(defaultDataSource);
        // 设置目标数据源
        dynamicRoutingDataSource.setTargetDataSources(targetDataSource);
        dynamicRoutingDataSource.afterPropertiesSet();
        log.info("Initializing dynamic routing datasource finished");
    }

    /**
     * refresh 目前针对plugin的刷新
     * 同时，只针对etcd监听
     *
     * @param tenantId
     * @return
     */
    public void refreshSourcePlugins(String tenantId) {
        Set<ShardDataSource> dataSourceList =
            this.dataShardSourceGateway.getSourceByTenantIds(Collections.singleton(tenantId));
        if (CollectionUtils.isEmpty(dataSourceList)) {
            return;
        }
        LogAggregateUtils.LogContext logContext = LogAggregateUtils.createLogContext();
        logContext.addLogContext("*******************Start refresh dataSource plugins*******************");
        for (ShardDataSource source : dataSourceList) {
            try {
                ShardSourceHolder.refreshSqlPluginCache(tenantId, source.buildSqlPlugins().get(tenantId));
                logContext.addLogContext("refresh datasource plugin from {} :[{}] success", source.getSource(), tenantId);
            } catch (Exception ex) {
                Cat.logEvent("DataShard",
                    "Load.Source",
                    Message.SUCCESS,
                    StringUtils.join("failed for loading tenantIds:", tenantId, ", the reason is ", ex.getMessage()));
                log.error(MessageFormatter.format("Load datasource:[{}] failed", tenantId).getMessage(), ex);
            }
        }
        logContext.addLogContext("********************************************************************");
        logContext.info(log);
    }
}
